package test;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;

import javax.xml.rpc.ServiceException;

import travel.caval._20091127.channelManager.AbstractOffer;
import travel.caval._20091127.channelManager.BoardPromotionOffer;
import travel.caval._20091127.channelManager.CavalGetFullHotelRatesRQ;
import travel.caval._20091127.channelManager.CavalGetFullHotelRatesRS;
import travel.caval._20091127.channelManager.ChannelManagerService;
import travel.caval._20091127.channelManager.Contract;
import travel.caval._20091127.channelManager.ContractedBoardType;
import travel.caval._20091127.channelManager.ContractedRoomType;
import travel.caval._20091127.channelManager.DatesRange;
import travel.caval._20091127.channelManager.Dinner;
import travel.caval._20091127.channelManager.DiscountOffer;
import travel.caval._20091127.channelManager.DiscountPercent;
import travel.caval._20091127.channelManager.EarlyBookingOffer;
import travel.caval._20091127.channelManager.Hotel;
import travel.caval._20091127.channelManager.MandatoryCheckInDaysConstraint;
import travel.caval._20091127.channelManager.MinimumStayConstraint;
import travel.caval._20091127.channelManager.PaxFreeOffer;
import travel.caval._20091127.channelManager.PaxPrice;
import travel.caval._20091127.channelManager.PriceOffer;
import travel.caval._20091127.channelManager.Rate;
import travel.caval._20091127.channelManager.RatePerRoom;
import travel.caval._20091127.channelManager.RatePerRoomAndBoard;
import travel.caval._20091127.channelManager.ReleaseConstraint;
import travel.caval._20091127.channelManager.RoomPromotionOffer;
import travel.caval._20091127.channelManager.StayXPayYOffer;
import travel.caval._20091127.channelManager.Stock;
import travel.caval._20091127.channelManager.StockPerRoom;
import travel.caval._20091127.channelManager.StopSales;
import travel.caval._20091127.channelManager.SupplementOrDiscount;
import travel.caval._20091127.hotelBooking.Amount;
import travel.caval._20091127.hotelBooking.AvailableEstablishment;
import travel.caval._20091127.hotelBooking.BoardPrice;
import travel.caval._20091127.hotelBooking.CavalHotelAvailabilityRQ;
import travel.caval._20091127.hotelBooking.CavalHotelAvailabilityRS;
import travel.caval._20091127.hotelBooking.CombinationPrice;
import travel.caval._20091127.hotelBooking.OfferCategory;
import travel.caval._20091127.hotelBooking.RoomOccupation;
import travel.caval.channelManager.ChannelManagerServiceLocator;

/**
 * calculates prices on caval's getFullHotelRates response
 * 
 * @author root
 *
 */
public class HotelRatesTester {

	public static void main(String[] args) throws ServiceException, RemoteException, ParseException, MalformedURLException {
		
		if (args.length < 13) {
			System.out.println("compares prices from getFullHotelRates and online availability (they should be the same)");
			System.out.println("usage: java cp=.... test.HotelRatesTester <ChannelManagerService end point url> <agentId> <login> <password> <iata> <checkin> <checkout> <hotelId> <roomId> <boardId> <numberOfRooms> <numberOfAdults> <numberOfChildren>");
			System.out.println("to reuse cached responses also include -Dreload=false ");
		} else {
			AvailabilityTester.reload = "true".equals(System.getProperty("reload", "true"));
			AvailabilityTester.channelManagerServiceUrl = args[0];

			CavalGetFullHotelRatesRS rs = getFullHotelRates(args[1], args[2], args[3], args[7], args[4]);
			
			if (rs.getHotels() != null) for (Hotel h : rs.getHotels()) {
				if (args[7].equals(h.getId())) {					
					System.out.println("price=" + getValue(h, args[5], args[6], args[8], args[9], new int[] {Integer.parseInt(args[10]),Integer.parseInt(args[11]),Integer.parseInt(args[12])}));
				}
			}
		}
	}

	/**
	 * call to remote getFullHotelRates or get it from a local cache  
	 * 
	 * @param agentId the caval agent id
	 * @param login the caval login
	 * @param password the caval password
	 * @param hotelId for filtering by hotel. Leave empty if you are looking for all hotels in a destination
	 * @param airportId airport IATA code for filtering by airport. Leave empty if you are looking for only a hotel
	 * @return the getFullHotelRates response
	 * @throws ServiceException
	 * @throws RemoteException
	 * @throws MalformedURLException 
	 */
	private static CavalGetFullHotelRatesRS getFullHotelRates(String agentId, String login, String password, String hotelId, String airportId) throws ServiceException, RemoteException, MalformedURLException {
		
		CavalGetFullHotelRatesRS rs = null;

		if (AvailabilityTester.reload) {
			ChannelManagerServiceLocator loc = new ChannelManagerServiceLocator();
			ChannelManagerService s = loc.getChannelManagerServiceImplPort(new URL(AvailabilityTester.channelManagerServiceUrl));

			CavalGetFullHotelRatesRQ rq = new CavalGetFullHotelRatesRQ();
			rq.setAgentId(agentId);
			rq.setLogin(login);
			rq.setPassword(password);
			rq.setLanguage("en");
			if (hotelId != null) rq.setHotelIds(new String[] {hotelId});
			if (airportId != null) rq.setAirportIds(new String[] {airportId});
			rs = s.getFullHotelRates(rq);


			//serialize the List
			try {
				OutputStream file = new FileOutputStream(System.getProperty("java.io.tmpdir") + "/rs0.ser");
				OutputStream buffer = new BufferedOutputStream(file);
				ObjectOutput output = new ObjectOutputStream(buffer);
				output.writeObject(rs);
				output.close();
			}  
			catch(IOException ex){
				ex.printStackTrace();
			}
		}

		//deserialize

		try {

			InputStream file = new FileInputStream(System.getProperty("java.io.tmpdir") + "/rs0.ser");
			InputStream buffer = new BufferedInputStream(file);
			ObjectInput input = new ObjectInputStream (buffer);


			//deserialize the List
			Object o = input.readObject();
			rs = (CavalGetFullHotelRatesRS) o;
		}
		catch(ClassNotFoundException ex){
			ex.printStackTrace();
		}
		catch(IOException ex){
			ex.printStackTrace();
		}
		
		if (rs.getResultCode() != 200) System.out.println("ERROR:" + rs.getMessage()); 
		else System.out.println("got " + ((rs.getHotels() != null)?rs.getHotels().length:0) + " hotels");
		
		return rs;
	}

	/**
	 * method to quickly test prices from a getFullHotelRates response
	 * 
	 * @param rs a getFullHotelRates response
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param occupation an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @return an string detailing the prices
	 * @throws RemoteException
	 * @throws ServiceException
	 * @throws ParseException
	 */
	public static String testAvailability(CavalGetFullHotelRatesRS rs, String checkin, String checkout, int[] occupation) throws RemoteException, ServiceException, ParseException {
		StringBuffer avrs = new StringBuffer();		
		
		// iterate over each hotel...
		for (Hotel h : rs.getHotels()) {
			avrs.append("--------------------------------------------------------------------\n");
			avrs.append("---" + h.getName() + " - " + h.getCityName()  + "---\n");
			avrs.append("--------------------------------------------------------------------\n");
			
			// collect room and board ids from contracts
			List<String> roomIds = new ArrayList<String>();
			List<String> boardIds = new ArrayList<String>();
			if (h.getContracts() != null) for (Contract c : h.getContracts()) {
				if (c.getRoomTypes() != null) for (ContractedRoomType r : c.getRoomTypes()) {
					if (!roomIds.contains(r.getCode())) roomIds.add(r.getCode()); 
				}
				if (c.getBoardTypes() != null) for (ContractedBoardType b : c.getBoardTypes()) {
					if (!boardIds.contains(b.getCode())) boardIds.add(b.getCode()); 
				}
			}
			
			// check each room + board combination for price and availability
			for (String roomId : roomIds) {
				for (String boardId : boardIds) if ("F.ROOM".equalsIgnoreCase(roomId) && "PC AYV".equalsIgnoreCase(boardId)) {
					try {
						double value = getValue(h, checkin, checkout, roomId, boardId, occupation);
						if (value > 0) {
							avrs.append(String.format("-->%20s / %-20s : %19.2f%n", roomId, boardId, value));
						}
					} catch (Exception e) {
						System.out.println("ERROR:" + e.getClass().getName() + " " + e.getMessage());
					}
				}
			}
		}
		return avrs.toString();
	}
	
	
	/**
	 * emulates a caval compliant availability request / response 
	 * @param rq the caval compliant availability request
	 * @return a caval compliant availability response
	 */
	public static CavalHotelAvailabilityRS getAvailableHotels(CavalHotelAvailabilityRQ rq) {
		CavalHotelAvailabilityRS rs = new CavalHotelAvailabilityRS();
		
		try {
			CavalGetFullHotelRatesRS frrs = getFullHotelRates(rq.getAgentId(), rq.getLogin(), rq.getPassword(), (rq.getEstablishmentIds() != null && rq.getEstablishmentIds().length > 0)?rq.getEstablishmentIds()[0]:null, (rq.getAirportIds() != null && rq.getAirportIds().length > 0)?rq.getAirportIds()[0]:null);
			List<AvailableEstablishment> aes = new ArrayList<AvailableEstablishment>();
			for (Hotel h : frrs.getHotels()) 
				if ("".equals(AvailabilityTester.idHotel) || AvailabilityTester.idHotel.equalsIgnoreCase(h.getId()))
				{
				
				List<String> roomIds = new ArrayList<String>();
				List<String> boardIds = new ArrayList<String>();
				if (h.getContracts() != null) for (Contract c : h.getContracts()) {
					if (c.getRoomTypes() != null) for (ContractedRoomType r : c.getRoomTypes()) {
						if (!roomIds.contains(r.getCode())) roomIds.add(r.getCode()); 
					}
					if (c.getBoardTypes() != null) for (ContractedBoardType b : c.getBoardTypes()) {
						if (!boardIds.contains(b.getCode())) boardIds.add(b.getCode()); 
					}
				}

				int[] occupation = new int[3 + ((rq.getOccupations()[0].getChildAges() != null)?rq.getOccupations()[0].getChildAges().length:0)];
				occupation[0] = rq.getOccupations()[0].getNumberOfRooms();
				occupation[1] = rq.getOccupations()[0].getAdultsPerRoom();
				occupation[2] = rq.getOccupations()[0].getChildrenPerRoom();
				if (rq.getOccupations()[0].getChildAges() != null) for (int i = 0; i < rq.getOccupations()[0].getChildAges().length; i++) {
					occupation[3 + i] = rq.getOccupations()[0].getChildAges()[i];
				}

				List<CombinationPrice> prices = new ArrayList<CombinationPrice>();
				for (String roomId : roomIds) {
					List<BoardPrice> bps = new ArrayList<BoardPrice>();
					for (String boardId : boardIds) 
					if ((AvailabilityTester.idRoom == null || "".equals(AvailabilityTester.idRoom) || AvailabilityTester.idRoom.equalsIgnoreCase(roomId)) 
							&&  (AvailabilityTester.idBoard == null || "".equals(AvailabilityTester.idBoard) || AvailabilityTester.idBoard.equalsIgnoreCase(boardId)))  //xxxxxxxxx
					{
						try {
							double value = getValue(h, rq.getCheckIn(), rq.getCheckOut(), roomId, boardId, occupation);
							if (value > 0) {
								bps.add(new BoardPrice(new OfferCategory[0], "", boardId, "", new Amount("EUR", 0), "", new Amount("EUR", value), false, "", new Amount("EUR", 0), new Amount("EUR", 0)));
							}
						} catch (Exception e) {
							System.out.println("ERROR:" + e.getClass().getName() + " " + e.getMessage());
						}
					}
					if (bps.size() > 0) {
						CombinationPrice cp = new CombinationPrice();
						cp.setRooms(new RoomOccupation[] { new RoomOccupation(rq.getOccupations()[0].getAdultsPerRoom(), rq.getOccupations()[0].getChildAges(), rq.getOccupations()[0].getChildrenPerRoom(), rq.getOccupations()[0].getNumberOfRooms(), roomId, "", "OK") });
						prices.add(cp);
						cp.setBoardPrices(bps.toArray(new BoardPrice[0]));
					}
				}
				
				if (prices.size() > 0) {
					AvailableEstablishment ae = new AvailableEstablishment();
					ae.setEstablishmentId(h.getId());
					ae.setEstablishmentName(h.getName());
					ae.setPrices(prices.toArray(new CombinationPrice[0]));
					aes.add(ae);
				}
			}
			
			rs.setAvailableEstablishments(aes.toArray(new AvailableEstablishment[0]));
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return rs ;
	}

	/**
	 * check availability and price for a concrete room and board combination in a hotel. Assumes you are interested in offers
	 * @param hotel the hotel
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param occupation an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @return the price or 0 if it is not available
	 * @throws ParseException
	 */
	private static double getValue(Hotel hotel, String checkin, String checkout, String roomId, String boardId, int[] occupation) throws ParseException {
		return getValue(hotel, checkin, checkout, roomId, boardId, occupation, true);
	}

	/**
	 * check availability and price for a concrete room and board combination in a hotel. 
	 * This version of the method allows you to say that you do not want offers to be applied. 
	 * This option is useful for getting the base price for offers and supplements / discounts 
	 * 
	 * @param hotel the hotel
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param occupation an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @param applyOffers true if you want offers to be applied
	 * @return the price or 0 if it is not available
	 * @throws ParseException
	 */
	private static double getValue(Hotel hotel, String checkin, String checkout, String roomId, String boardId, int[] occupation, boolean applyOffers) throws ParseException {

		double bestValue = 99999999;

		int numberOfRooms = occupation[0];
		int numberOfPax = occupation[0] * (occupation[1] + occupation[2]);

		for (Contract c : hotel.getContracts()) {
			System.out.println("looking at contract " + c.getTitle());			
			if (check(c, roomId, occupation) && check(hotel, c, checkin, checkout, roomId, boardId, numberOfRooms, numberOfPax)) {
				double contractValue = getContractValue(hotel, c, checkin, checkout, roomId, boardId, occupation, applyOffers);
				if (contractValue > 0 && bestValue > contractValue) bestValue = contractValue;
			}

		}

		return (bestValue < 99999999)?bestValue:0;
	}

	/**
	 * checks if a pax fit a room
	 * 
	 * @param c the contract
	 * @param roomId the room id
	 * @param occupation an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @return
	 */
	private static boolean check(Contract c, String roomId, int[] occupation) {
		boolean ok = false;
		
		ContractedRoomType r = null;
		if (c.getRoomTypes() != null) for (ContractedRoomType x : c.getRoomTypes()) {
			if (roomId.equals(x.getCode())) r = x;
		}
		
		if (r != null) {
			ok = true;

				int numberOfRooms = occupation[0];
				int adultsPerRoom = occupation[1];
				int childrenPerRoom = occupation[2];

				int[][] childrenAges = new int[numberOfRooms][childrenPerRoom];

				if (occupation.length > 3) {
					int[] passedChildrenAges = Arrays.copyOfRange(occupation, 3, occupation.length);
					Arrays.sort(passedChildrenAges);
					int pos = 0;			
					for (int j = childrenPerRoom - 1; j >= 0; j--) for (int i = 0; i < numberOfRooms; i++)childrenAges[i][j] = (passedChildrenAges.length > pos)?passedChildrenAges[pos]:11;
				} else {
					for (int j = childrenPerRoom - 1; j >= 0; j--) for (int i = 0; i < numberOfRooms; i++)childrenAges[i][j] = 11;
				}

				for (int i = 0; i < numberOfRooms; i++) {				
					int realInfantsInRoom = 0;
					int promotedChildren = 0;
					for (int j = 0; j < childrenAges[i].length; j++) {						
						if (c.getInfantMaxAge() != null && childrenAges[i][j] <= c.getInfantMaxAge()) realInfantsInRoom++;
						if (c.getChildMaxAge() > 0 && childrenAges[i][j] > c.getChildMaxAge()) {
							if (c.getYouthMaxAge() == null || c.getYouthMaxAge() <= 0 || childrenAges[i][j] > c.getYouthMaxAge()) {
								promotedChildren++;
							}
						}
					}
					int realAdultsInThisRoom = adultsPerRoom + promotedChildren;
					int realChildrenInThisRoom = childrenPerRoom - promotedChildren - realInfantsInRoom;
					int realPax = realAdultsInThisRoom + realChildrenInThisRoom + ((r.isInfantsOccupyBed())?realInfantsInRoom:0);
					ok &= r.isChildrenAllowed() || (realChildrenInThisRoom == 0 && realInfantsInRoom == 0);
					ok &= r.isInfantsAllowed() || realInfantsInRoom == 0;
					ok &= r.getMinPax() <= realPax;					
					
					boolean roomOk = false;
					if (r != null && r.getMaxOccupation() != null){
						StringTokenizer st = new StringTokenizer(r.getMaxOccupation().trim(), " ;,.óÓoOy/");
						while(st.hasMoreElements()){
							String[] auxx = st.nextToken().split("\\+");
							if (auxx.length == 1) {
								roomOk |= realPax <= Integer.parseInt(auxx[0]);
							} else if (auxx.length >= 2) {
								int ad = Integer.parseInt(auxx[0]);
								int ni = Integer.parseInt(auxx[1]);
								roomOk |= realAdultsInThisRoom <= ad  && realAdultsInThisRoom + realChildrenInThisRoom  + ((r.isInfantsOccupyBed())?realInfantsInRoom:0) <= ad + ni;
							}
						}
					}
					
					ok &= roomOk;
				}

		}
			
		return ok;
	}

	/**
	 * checks the different restrictions (stop sales, minimum stay, etc) to verify that the booking is possible 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param numberOfRooms the number of rooms (here we do not need the board type)
	 * @param numberOfPax the number of pax (here we do not need the detail)
	 * @return true if there is no problem
	 * @throws ParseException
	 */
	private static boolean check(Hotel hotel, Contract c, String checkin, String checkout, String roomId, String boardId, int numberOfRooms, int numberOfPax) throws ParseException {
		boolean ok = true;

		ok &= checkStopSales(hotel, c, checkin, checkout, roomId, boardId);

		ok &= c.getFreeSale() || checkStock(hotel, c, checkin, checkout, roomId, boardId, numberOfRooms);

		ok &= checkMinimumStay(hotel, c, checkin, checkout, roomId, boardId);

		ok &= checkRelease(hotel, c, checkin, checkout, roomId, boardId);

		ok &= checkMandatoryCheckinAndCheckoutDates(hotel, c, checkin, checkout, roomId, boardId);

		ok &= checkOtherContractTerms(hotel, c, checkin, checkout, roomId, boardId, numberOfRooms, numberOfPax);

		System.out.println("checks result..." + (ok?"ok":"ko"));

		return ok;
	}


	/**
	 * checks for occupation limits at contract level
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param numberOfRooms the number of rooms (here we do not need the board type)
	 * @param numberOfPax the number of pax (here we do not need the detail)
	 * @return true if there is no problem
	 * @return
	 */
	private static boolean checkOtherContractTerms(Hotel hotel, Contract c, String checkin, String checkout, String roomId, String boardId, int numberOfRooms, int numberOfPax) {
		boolean ok = c.getMaxPaxPerBooking() >= numberOfPax && c.getMaxRoomsPerBooking() >= numberOfRooms; 
		System.out.println("other checks..." + (ok?"ok":"ko"));
		return ok;
	}

	/**
	 *	checks for mandatory check in and check out restrictions
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @return true if there is no problem
	 * @throws ParseException
	 */
	private static boolean checkMandatoryCheckinAndCheckoutDates(Hotel hotel, Contract c, String checkin, String checkout, String roomId, String boardId) throws ParseException {
		boolean ok = true;

		if (c.getMandatoryCheckInDayConstraints() != null) for (MandatoryCheckInDaysConstraint m : c.getMandatoryCheckInDayConstraints()) {
			if (getNight(checkin, m.getDateTo()) >= 0 && getNight(m.getDateFrom(), checkout) > 0) {

				if (m.getAllowedWeekDaysForCheckin() != null) {
					ok = false;
					int weekDay = new SimpleDateFormat("dd/MM/yyyy").parse(checkin).getDay(); // 0 == sunday
					if (m.getAllowedWeekDaysForCheckin().isMon() && weekDay == 1
							|| m.getAllowedWeekDaysForCheckin().isTue() && weekDay == 2
							|| m.getAllowedWeekDaysForCheckin().isWed() && weekDay == 3
							|| m.getAllowedWeekDaysForCheckin().isThu() && weekDay == 4
							|| m.getAllowedWeekDaysForCheckin().isFri() && weekDay == 5
							|| m.getAllowedWeekDaysForCheckin().isSat() && weekDay == 6
							|| m.getAllowedWeekDaysForCheckin().isSun() && weekDay == 0
							) ok = true; 
				}
				if (m.getAllowedWeekDaysForCheckout() != null) {
					ok = false;
					int weekDay = new SimpleDateFormat("dd/MM/yyyy").parse(checkout).getDay(); // 0 == sunday
					if (m.getAllowedWeekDaysForCheckout().isMon() && weekDay == 1
							|| m.getAllowedWeekDaysForCheckout().isTue() && weekDay == 2
							|| m.getAllowedWeekDaysForCheckout().isWed() && weekDay == 3
							|| m.getAllowedWeekDaysForCheckout().isThu() && weekDay == 4
							|| m.getAllowedWeekDaysForCheckout().isFri() && weekDay == 5
							|| m.getAllowedWeekDaysForCheckout().isSat() && weekDay == 6
							|| m.getAllowedWeekDaysForCheckout().isSun() && weekDay == 0
							) ok = true; 
				}
				if (m.getAllowedMonthDays() != null && m.getAllowedMonthDays().length > 0) {
					ok = false;
					int monthDay = new SimpleDateFormat("dd/MM/yyyy").parse(checkin).getDate();
					for (int allowedDay : m.getAllowedMonthDays()) ok |= allowedDay == monthDay;
				}
			}
			if (!ok) break;
		}

		System.out.println("mandatory checkin/checkout..." + (ok?"ok":"ko"));

		return ok;
	}

	/**
	 * checks for release restrictions
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @return true if there is no problem
	 * @throws ParseException
	 */
	private static boolean checkRelease(Hotel hotel, Contract c, String checkin, String checkout, String roomId, String boardId) throws ParseException {
		int release = getNumberOfNights(new SimpleDateFormat("dd/MM/yyyy").format(new Date()), checkin);
		boolean ok = true;

		if (c.getReleaseConstraints() != null) for (ReleaseConstraint m : c.getReleaseConstraints()) {
			if (getNight(checkin, m.getToDate()) >= 0 && getNight(m.getFromDate(), checkout) > 0) {
				if (m.getRelease() > release) {
					ok &= false;
				}
			}
			if (!ok) break;
		}
		System.out.println("release..." + (ok?"ok":"ko"));
		return ok;
	}

	/**
	 *	checks for minimum stay restriction. Please notice that we check again minimum stay to apply supplements
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @return true if there is no problem
	 * @throws ParseException
	 */
	private static boolean checkMinimumStay(Hotel hotel, Contract c, String checkin, String checkout, String roomId, String boardId) throws ParseException {
		int numberOfNights = getNumberOfNights(checkin, checkout);
		boolean ok = true;

		if (c.getMinimumStayConstraints() != null) for (MinimumStayConstraint m : c.getMinimumStayConstraints()) {
			if (getNight(checkin, m.getToDate()) >= 0 && getNight(m.getFromDate(), checkout) > 0) {
				if (m.getMinimumNights() > numberOfNights) {
					ok &= !m.isOnRequestIfLess() && (m.getSupplementPercentIfLess() > 0 || m.getSupplementPerPaxIfLess() > 0);
				}
			}
			if (!ok) break;
		}

		System.out.println("minimum stay..." + (ok?"ok":"ko"));
		return ok;
	}

	/**
	 * apply minimum stay supplements
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param base base price per night. Supplement is applied on this
	 * @param supplementsPerBooking if %, the supplement will be added here
	 * @param supplementsPerPax if amount per pax, the supplement will be added here
	 * @throws ParseException
	 */
	private static void applyMinimumStaySupplements(Hotel hotel, Contract c, String checkin, String checkout, String roomId, String boardId, double[] base, double[] supplementsPerBooking, double[][][] supplementsPerPax) throws ParseException {
		int numberOfNights = getNumberOfNights(checkin, checkout);

		if (c.getMinimumStayConstraints() != null) for (MinimumStayConstraint m : c.getMinimumStayConstraints()) {
			if (getNight(checkin, m.getToDate()) >= 0 && getNight(m.getFromDate(), checkout) > 0) {
				if (m.getMinimumNights() > numberOfNights) {
					
					int from = getNight(checkin, m.getFromDate());
					if (from < 0) from = 0;
					int until = getNight(m.getToDate(), checkout);
					if (until < 0 || until >= numberOfNights) until = numberOfNights - 1;
					
					if (m.getSupplementPercentIfLess() > 0) {
						for (int n = from; n <= until; n++) {
							supplementsPerBooking[n] += applySupplement(base[n], "+" + m.getSupplementPercentIfLess() + "%") - base[n];
						}
					}
					if (m.getSupplementPerPaxIfLess() > 0) {
						for (int n = from; n <= until; n++) {
							for (int room = 0; room < supplementsPerPax.length; room++) {
								for (int pax = 0; pax < supplementsPerPax[room][n].length; pax++) {								
									supplementsPerPax[room][n][pax] += applySupplement(base[n], "+" + m.getSupplementPerPaxIfLess()) - base[n];
								}
							}
						}
					}
				}
			}
		}

	}

	/**
	 * check if there are available rooms
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param numberOfRooms number of rooms
	 * @return true if there are available rooms
	 * @throws ParseException
	 */
	private static boolean checkStock(Hotel hotel, Contract c, String checkin, String checkout, String roomId, String boardId, int numberOfRooms) throws ParseException {
		int numberOfNights = getNumberOfNights(checkin, checkout);
		int[] availableRooms = new int[numberOfNights];

		if (c.getStocks() != null) for (Stock s : c.getStocks()) {
			for (String rc : s.getRoomCodes()) if (roomId.equals(rc)) {
				for (int i = getNight(checkin, s.getFromDate()); i <= getNight(checkin, s.getToDate()); i++) if (i >= 0 && i < availableRooms.length) {
					availableRooms[i] += s.getAvailableRooms();					
				}
				break;
			}
		}

		boolean ok = true;

		for (int i = 0; i < availableRooms.length; i++) ok &= availableRooms[i] >= numberOfRooms; 

		System.out.println("stock..." + (ok?"ok":"ko"));

		return ok;
	}

	/**
	 * check for stop sales
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @return true if there are available rooms
	 * @throws ParseException
	 */
	private static boolean checkStopSales(Hotel hotel, Contract c, String checkin, String checkout, String roomId, String boardId) throws ParseException {
		boolean closed = false;
		if (hotel.getStopSales() != null) for (StopSales s : hotel.getStopSales()) {
			for (DatesRange dr : s.getDates()) {
				if (getNight(checkin, dr.getTo()) >= 0 && getNight(dr.getFrom(), checkout) > 0) {
					boolean contractIncluded = s.getContractIds() == null || s.getContractIds().length == 0;
					boolean roomIncluded = s.getRoomIds() == null || s.getRoomIds().length == 0;
					boolean boardIncluded = s.getBoardIds() == null || s.getBoardIds().length == 0;

					if (!contractIncluded) for (String i : s.getContractIds()) if (c.getId().equals(i)) {
						contractIncluded = true;
						break;
					}

					if (!roomIncluded) for (String i : s.getRoomIds()) if (roomId.equals(i)) {
						roomIncluded = true;
						break;
					}

					if (!boardIncluded) for (String i : s.getBoardIds()) if (boardId.equals(i)) {
						contractIncluded = true;
						break;
					}

					closed = contractIncluded && roomIncluded && boardIncluded && s.getAction().equalsIgnoreCase("closed");
				}
				if (closed) break;
			}
			if (closed) break;
		}

		System.out.println("closed..." + (closed?"yes":"no"));

		return !closed;
	}

	/**
	 * looks if there is any applicable offer and gets the best option
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param boardType the board type group according to caval specification (BB, HB, ...)  
	 * @param occupation an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @param contractValue booking total price before applying offers 
	 * @param pricePerRoom price for each room before applying offers
	 * @param pricePerPax price for each pax before applying offers
	 * @param pricePerBoard price for each pax due to board
	 * @param discountsPerPax third pax and child discounts (and single use supplement) per pax
	 * @param supplementsPerBookingNoOfferApplicable supplements per booking that must be left out of the offers
	 * @param supplementsPerRoomNoOfferApplicable supplements per room that must be left out of the offers
	 * @param supplementsPerPaxNoOfferApplicable supplements per pax that must be left out of the offers
	 * @param supplementsPerBookingOfferApplicable supplements per booking that can be taken in account by the offers
	 * @param supplementsPerRoomOfferApplicable supplements per room that can be taken in account by the offers
	 * @param supplementsPerPaxOfferApplicable supplements per pax that can be taken in account by the offers
	 * @return the price of the booking with the best offer
	 * @throws ParseException
	 */
	private static double applyOffers(Hotel hotel, Contract c, String checkin,
			String checkout, String roomId, String boardId, String boardType,
			int[] occupation, double contractValue, double[][] pricePerRoom,
			double[][][] pricePerPax, double[][][] pricePerBoard,
			double[][][] discountsPerPax,
			double[] supplementsPerBookingNoOfferApplicable,
			double[][] supplementsPerRoomNoOfferApplicable,
			double[][][] supplementsPerPaxNoOfferApplicable,
			double[] supplementsPerBookingOfferApplicable,
			double[][] supplementsPerRoomOfferApplicable,
			double[][][] supplementsPerPaxOfferApplicable) throws ParseException {

		List<AbstractOffer> offers = new ArrayList<AbstractOffer>();
		if (hotel.getPriceOffers() != null) offers.addAll(Arrays.asList(hotel.getPriceOffers()));
		if (hotel.getBoardPromotionOffers() != null) offers.addAll(Arrays.asList(hotel.getBoardPromotionOffers()));
		if (hotel.getRoomPromotionOffers() != null) offers.addAll(Arrays.asList(hotel.getRoomPromotionOffers()));
		if (hotel.getDiscountOffers() != null) offers.addAll(Arrays.asList(hotel.getDiscountOffers()));
		if (hotel.getEarlyBookingOffers() != null) offers.addAll(Arrays.asList(hotel.getEarlyBookingOffers()));
		if (hotel.getPaxFreeOffers() != null) offers.addAll(Arrays.asList(hotel.getPaxFreeOffers()));
		if (hotel.getStayXPayYOffers() != null) offers.addAll(Arrays.asList(hotel.getStayXPayYOffers()));

		// filter valid offers
		List<AbstractOffer> validOffers = new ArrayList<AbstractOffer>();
		for (AbstractOffer o : offers) {
			if (check(o, checkin, checkout, roomId, boardId, occupation)) validOffers.add(o);
		}

		// join offers if possible
		List<Object> finalOffersList = new ArrayList<Object>();
		List<AbstractOffer> seen = new ArrayList<AbstractOffer>();
		for (AbstractOffer o : validOffers) {
			if (!seen.contains(o)) {
				if (o.getAccumulableOfferIds() != null && o.getAccumulableOfferIds().length > 0) {
					List<AbstractOffer> l;
					finalOffersList.add(l = new ArrayList<AbstractOffer>());
					l.add(o);
					seen.add(o);
					for (AbstractOffer x : validOffers) {
						boolean ok = false;
						if (o.getAccumulableOfferIds() != null) for (String id : o.getAccumulableOfferIds()) if (x.getId() != null && x.getId().equals(id)) ok = true;
						if (ok) {
							l.add(x);
							seen.add(x);
						}
					}
				} else {
					finalOffersList.add(o);
					seen.add(o);
				}
			}
		}

		double bestValue = contractValue;
		Object bestOffer = null;
		for (Object o : finalOffersList) {

			double[][] auxPricePerRoom = pricePerRoom.clone();
			double[][][] auxPricePerPax = pricePerPax.clone();
			double[][][] auxPricePerBoard = pricePerBoard.clone();
			double[][][] auxDiscountsPerPax = discountsPerPax.clone();

			double totalDiscount = 0;
			if (o instanceof List<?>) {
				List<AbstractOffer> l = orderOffers((List<AbstractOffer>) o);
				o = l;
				for (AbstractOffer x : l) {
					totalDiscount += applyOffer(x, hotel, c, checkin, checkout, roomId, boardId, boardType, occupation, contractValue, auxPricePerRoom, auxPricePerPax, auxPricePerBoard, auxDiscountsPerPax, supplementsPerBookingNoOfferApplicable, supplementsPerRoomNoOfferApplicable, supplementsPerPaxNoOfferApplicable, supplementsPerBookingOfferApplicable, supplementsPerRoomOfferApplicable, supplementsPerPaxOfferApplicable, totalDiscount);
				}
			} else {
				totalDiscount = applyOffer((AbstractOffer) o, hotel, c, checkin, checkout, roomId, boardId, boardType, occupation, contractValue, auxPricePerRoom, auxPricePerPax, auxPricePerBoard, auxDiscountsPerPax, supplementsPerBookingNoOfferApplicable, supplementsPerRoomNoOfferApplicable, supplementsPerPaxNoOfferApplicable, supplementsPerBookingOfferApplicable, supplementsPerRoomOfferApplicable, supplementsPerPaxOfferApplicable, totalDiscount);
			}
			if (bestValue > contractValue - totalDiscount) {
				bestValue = contractValue - totalDiscount;
				bestOffer = o;
			}
		}

		if (bestOffer != null) {
			String msg = "applied offer ";
			if (bestOffer instanceof List<?>) {
				for (Object o : (List<Object>) bestOffer) {
					if (!"applied offer ".equals(msg)) msg += ", ";
					msg += ((AbstractOffer)o).getDescription();
				}
			} else {
				msg += ((AbstractOffer)bestOffer).getDescription();
			}
			msg += " with a final value of " + bestValue;
			System.out.println(msg);
		} else {
			System.out.println("no offer applied");
		}
		return bestValue;
	}

	private static List<AbstractOffer> orderOffers(List<AbstractOffer> l) {
		List<AbstractOffer> l2 = new ArrayList<AbstractOffer>();
		
		ArrayList<PriceOffer> po = new ArrayList<PriceOffer>();
		ArrayList<BoardPromotionOffer> bpo = new ArrayList<BoardPromotionOffer>();
		ArrayList<RoomPromotionOffer> rpo = new ArrayList<RoomPromotionOffer>();
		ArrayList<PaxFreeOffer> pfo = new ArrayList<PaxFreeOffer>();
		ArrayList<StayXPayYOffer> spo = new ArrayList<StayXPayYOffer>();
		ArrayList<DiscountOffer> dso = new ArrayList<DiscountOffer>();
		ArrayList<EarlyBookingOffer> ebo = new ArrayList<EarlyBookingOffer>();
		for (AbstractOffer ao : l) {
			if (ao instanceof PriceOffer) po.add((PriceOffer) ao);
			if (ao instanceof BoardPromotionOffer) bpo.add((BoardPromotionOffer) ao);
			if (ao instanceof RoomPromotionOffer)rpo.add((RoomPromotionOffer) ao);
			if (ao instanceof PaxFreeOffer) pfo.add((PaxFreeOffer) ao);
			if (ao instanceof StayXPayYOffer) spo.add((StayXPayYOffer) ao);
			if (ao instanceof DiscountOffer) dso.add((DiscountOffer) ao);
			if (ao instanceof EarlyBookingOffer) ebo.add((EarlyBookingOffer) ao);
		}
		for (AbstractOffer o : po) l2.add(o);
		for (AbstractOffer o : bpo) l2.add(o);
		for (AbstractOffer o : rpo) l2.add(o);
		for (AbstractOffer o : pfo) l2.add(o);
		for (AbstractOffer o : spo) l2.add(o);
		for (AbstractOffer o : dso) l2.add(o);
		for (AbstractOffer o : ebo) l2.add(o);
		return l2;
	}

	/**
	 *  apply a particular offer
	 *  
	 * @param o the offer
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param boardType the board type group according to caval specification (BB, HB, ...)  
	 * @param occupation an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @param contractValue booking total price before applying offers 
	 * @param pricePerRoom price for each room before applying offers
	 * @param pricePerPax price for each pax before applying offers
	 * @param pricePerBoard price for each pax due to board
	 * @param discountsPerPax third pax and child discounts (and single use supplement) per pax
	 * @param supplementsPerBookingNoOfferApplicable supplements per booking that must be left out of the offers
	 * @param supplementsPerRoomNoOfferApplicable supplements per room that must be left out of the offers
	 * @param supplementsPerPaxNoOfferApplicable supplements per pax that must be left out of the offers
	 * @param supplementsPerBookingOfferApplicable supplements per booking that can be taken in account by the offers
	 * @param supplementsPerRoomOfferApplicable supplements per room that can be taken in account by the offers
	 * @param supplementsPerPaxOfferApplicable supplements per pax that can be taken in account by the offers
	 * @param totalDiscount 
	 * @return the discount for the booking if this offer is applied
	 * @throws ParseException
	 */
	private static double applyOffer(AbstractOffer o, Hotel hotel, Contract c, String checkin,
			String checkout, String roomId, String boardId, String boardType,
			int[] occupation, double contractValue, double[][] pricePerRoom,
			double[][][] pricePerPax, double[][][] pricePerBoard,
			double[][][] discountsPerPax, double[] supplementsPerBookingNoOfferApplicable,
			double[][] supplementsPerRoomNoOfferApplicable,
			double[][][] supplementsPerPaxNoOfferApplicable,
			double[] supplementsPerBookingOfferApplicable,
			double[][] supplementsPerRoomOfferApplicable,
			double[][][] supplementsPerPaxOfferApplicable, double totalDiscount) throws ParseException {

		boolean[] applicable = new boolean[pricePerRoom[0].length];
		if (o.getAllowedStays() != null) for (DatesRange dr : o.getAllowedStays()) {
			for (int i = getNight(checkin, dr.getFrom()); i < getNight(checkin, dr.getTo()); i++) if (i >= 0 && i < applicable.length) {
				applicable[i] = true;
			}
		} else for (int x=0; x < applicable.length; x++) applicable[x] = true;

		double discount = 0;

		double[] base = getBookingExplodedPriceForOffers(pricePerRoom, pricePerPax, pricePerBoard, discountsPerPax, supplementsPerBookingOfferApplicable, supplementsPerRoomOfferApplicable, supplementsPerPaxOfferApplicable);
		double discountPerDay = 0;
		if (totalDiscount > 0) {
			discountPerDay = totalDiscount/base.length;
			for (int x = 0; x < base.length; x++) {
				base[x] = base[x]-discountPerDay;
			}
		}
		double totalBase = 0;
		for (int i = 0; i < base.length; i++) totalBase += base[i];

		if (o instanceof PriceOffer) {
			PriceOffer po = (PriceOffer) o;
			int ir = 0;
			if (po.getRoomIds() != null) for (int j = 0; j < po.getRoomIds().length; j++) if (roomId.equals(po.getRoomIds()[j])) ir = j;
			int ib = 0;
			if (po.getRatesPerRoom() != null) for (int j = 0; j < po.getRatesPerRoom()[ir].getRatesPerBoard().length; j++) if (boardId.equals(po.getRatesPerRoom()[ir].getRatesPerBoard()[j].getBoardCode())) ib = j;

			if (po.getPricePerRoom() != null) {
				double price = 0;
				if (po.getPricePerRoom()[ir].isPricePerRoom()) {
					if (po.getRatesPerRoom()[ir].getRatesPerBoard()[ib].getBasePrice() != null && !"".equals(po.getRatesPerRoom()[ir].getRatesPerBoard()[ib].getBasePrice())) { 
						for (int room = 0; room < pricePerRoom.length; room++) {
							if (applicable[room]) {
								for (int night = 0; night < pricePerRoom[room].length; night++) {
									price = pricePerRoom[room][night];
									pricePerRoom[room][night] = applySupplement(pricePerRoom[room][night], po.getRatesPerRoom()[ir].getRatesPerBoard()[ib].getBasePrice());
									for (int pax = 0; pax < pricePerPax[room][night].length; pax++) {
										pricePerPax[room][night][pax] = 0;
										discountsPerPax[room][night][pax] = 0;										
									}
									discount += price - pricePerRoom[room][night];
								}
							}
						}
					}
				} else {
					for (int room = 0; room < pricePerRoom.length; room++) {
						for (int night = 0; night < pricePerRoom[room].length; night++) {
							if (po.getRatesPerRoom()[ir].getRatesPerBoard()[ib].getBasePrice() != null && !"".equals(po.getRatesPerRoom()[ir].getRatesPerBoard()[ib].getBasePrice())) pricePerRoom[room][night] = 0;
							for (int pax = 0; pax < pricePerPax[room][night].length; pax++) {
								if (po.getRatesPerRoom()[ir].getRatesPerBoard()[ib].getBasePrice() != null && !"".equals(po.getRatesPerRoom()[ir].getRatesPerBoard()[ib].getBasePrice())) {
									price = pricePerPax[room][night][pax];
									pricePerPax[room][night][pax] = applySupplement(pricePerPax[room][night][pax], po.getRatesPerRoom()[ir].getRatesPerBoard()[ib].getBasePrice());
									discountsPerPax[room][night][pax] = price - pricePerPax[room][night][pax];
									discount += discountsPerPax[room][night][pax];
								}
								
							}
						}
					}
				}
			}
		} else if (o instanceof StayXPayYOffer) {
			StayXPayYOffer so = (StayXPayYOffer) o;
			boolean[] nights = new boolean[pricePerRoom[0].length];
			int firstNight = 898798797;
			int lastNight = -1;
			if (so.getAllowedStays() != null) for (DatesRange dr : so.getAllowedStays()) {
				int aux = getNight(checkin, dr.getFrom());
				if (aux >= 0 && aux < firstNight) firstNight = aux; 
			}
			if (firstNight == 898798797) firstNight = 0;
			if (lastNight == -1) lastNight = nights.length;
			int veces = (nights.length/so.getStay());
			//When penalty is number of nights, this value says which nights are to be applied.Possible values are 'FIRST', 'LAST', 'CHEAPEST', 'MOREEXPENSIVE' and 'AVERAGE'.
			if ("FIRST".equals(so.getWhichNights())) {
				for (int i = 0; i < so.getStay() - so.getPay(); i++) discount += base[firstNight + i];
				discount = discount*veces;
			} else if ("LAST".equals(so.getWhichNights())) {
				for (int i = 0; i < so.getStay() - so.getPay(); i++) discount += base[lastNight - i];
				discount = discount*veces;
			} else if ("CHEAPEST".equals(so.getWhichNights())) {
				List<Double> selectedPrices = new ArrayList<Double>();
				for (int i = 0; i < so.getStay(); i++) selectedPrices.add(base[firstNight + i]);
				Collections.sort(selectedPrices);				
				for (int i = 0; i < so.getStay() - so.getPay(); i++) discount += selectedPrices.get(i);
				discount = discount*veces;
			} else if ("MOREEXPENSIVE".equals(so.getWhichNights())) {
				List<Double> selectedPrices = new ArrayList<Double>();
				for (int i = 0; i < so.getStay(); i++) selectedPrices.add(base[firstNight + i]);
				Collections.sort(selectedPrices);				
				Collections.reverse(selectedPrices);
				for (int i = 0; i < so.getStay() - so.getPay(); i++) discount += selectedPrices.get(i);
				discount = discount*veces;
			} else if ("AVERAGE".equals(so.getWhichNights())) {
				double selectedNightsTotal = 0;
				for (int i = 0; i < so.getStay(); i++) selectedNightsTotal += base[firstNight + i];
				discount += (so.getStay() - so.getPay()) * selectedNightsTotal / so.getStay();
				discount = discount*veces;
			}
		} else if (o instanceof BoardPromotionOffer) {
			BoardPromotionOffer bp = (BoardPromotionOffer) o;
			if (boardId.equals(bp.getPromotedBoardCode())) {
				double value0 = getValue(hotel, checkin, checkout, roomId, bp.getPromotedBoardCode(), occupation, false); 
				double value1 = getValue(hotel, checkin, checkout, roomId, bp.getPayAsBoardCode(), occupation, false);
				discount = value1 - value0;
			}
		} else if (o instanceof DiscountOffer) {
			DiscountOffer d = (DiscountOffer) o;
			if (d.isPercentage()) {
				discount = totalBase * d.getDiscount() / 100d;
			} else {
				double factor = 1;				
				if ("NIGHT".equalsIgnoreCase(d.getApplyFor())) {
					factor = applicable.length;
				} else if ("BOOKING".equalsIgnoreCase(d.getApplyFor())) {
					factor = 1;
				}
				int numberOfRooms = occupation[0]; 
				int numberOfPax = occupation[0] * (occupation[1] + occupation[2]);

				if ("PAX".equalsIgnoreCase(d.getApplyPer())) {
					factor = numberOfPax;  
				} else if ("ROOM".equalsIgnoreCase(d.getApplyPer())) {
					factor = numberOfRooms;  
				} else if ("SERVICE".equalsIgnoreCase(d.getApplyPer())) {
					if ("RO".equalsIgnoreCase(boardType)) {
						factor = 0;
					} else if ("SC".equalsIgnoreCase(boardType)) {						
						factor = 0;
					} else if ("BB".equalsIgnoreCase(boardType)) {						
						factor = 1;
					} else if ("HB".equalsIgnoreCase(boardType)) {						
						factor = 2;
					} else if ("FB".equalsIgnoreCase(boardType)) {						
						factor = 3;
					} else if ("AI".equalsIgnoreCase(boardType)) {						
						factor = 3;
					}
				} else if ("BOOKING".equalsIgnoreCase(d.getApplyPer())) {
					factor = 1;
				}
				discount = factor * d.getDiscount();
			}
		} else if (o instanceof EarlyBookingOffer) {
			EarlyBookingOffer eb = (EarlyBookingOffer) o;
			double percent = 0;		
			double release = getNumberOfNights(new SimpleDateFormat("dd/MM/yyyy").format(new Date()), checkin);
			double minRelease = 999999;
			for (DiscountPercent p : eb.getDiscountPercentPerRelease()) {
				if (p.getRelease() > release && minRelease > p.getRelease()) {
					minRelease = p.getRelease();
					percent = p.getDiscountPercent();
				}
			}
			discount = totalBase * percent / 100d;
		} else if (o instanceof PaxFreeOffer) {
			PaxFreeOffer pf = (PaxFreeOffer) o;
			
			double[][][] discounts = new double[pricePerPax.length][pricePerPax[0].length][pricePerPax[0][0].length];
			
			int numberOfRooms = occupation[0];
			int adultsPerRoom = occupation[1];
			int childrenPerRoom = occupation[2];
			
			ContractedRoomType r = null;
			for (ContractedRoomType rx : c.getRoomTypes()) {
				if (roomId.equals(r.getCode())) r = rx;
			}

			int[][] childrenAges = new int[numberOfRooms][childrenPerRoom];

			if (occupation.length > 3) {
				int[] passedChildrenAges = Arrays.copyOfRange(occupation, 3, occupation.length);
				Arrays.sort(passedChildrenAges);
				int pos = 0;			
				for (int j = childrenPerRoom - 1; j >= 0; j--) for (int i = 0; i < numberOfRooms; i++)childrenAges[i][j] = (passedChildrenAges.length > pos)?passedChildrenAges[pos]:11;
			} else {
				for (int j = childrenPerRoom - 1; j >= 0; j--) for (int i = 0; i < numberOfRooms; i++)childrenAges[i][j] = 11;
			}

			for (int i = 0; i < numberOfRooms; i++) {				
				int realInfantsInRoom = 0;
				int realYouthsInRoom = 0;
				int promotedChildren = 0;
				for (int j = 0; j < childrenAges[i].length; j++) {						
					if (c.getInfantMaxAge() != null && childrenAges[i][j] <= c.getInfantMaxAge()) realInfantsInRoom++;
					if (c.getChildMaxAge() > 0 && childrenAges[i][j] > c.getChildMaxAge()) {
						if (c.getYouthMaxAge() != null && c.getYouthMaxAge() > 0 && childrenAges[i][j] <= c.getYouthMaxAge()) {
							realYouthsInRoom++;
						}
						if (c.getYouthMaxAge() == null || c.getYouthMaxAge() <= 0 || childrenAges[i][j] > c.getYouthMaxAge()) {
							promotedChildren++;
						}
					}
				}
				int realAdultsInThisRoom = adultsPerRoom + promotedChildren;
				int realChildrenInThisRoom = childrenPerRoom - promotedChildren - realYouthsInRoom - realInfantsInRoom;
				int realPax = realAdultsInThisRoom + realYouthsInRoom + realChildrenInThisRoom + ((r.isInfantsOccupyBed())?realInfantsInRoom:0);

				if (pf.getFreePax() != null) for (int p = 0; p < pf.getFreePax().length; p++) {
					if (pf.getFreePax()[p] < pricePerPax[i].length) {
						for (int n = 0; n < applicable.length; n++) if (applicable[n]) {
							discounts[i][pf.getFreePax()[p]][n] += pricePerPax[i][pf.getFreePax()[p]][n];
							discounts[i][pf.getFreePax()[p]][n] += pricePerBoard[i][pf.getFreePax()[p]][n];
							discounts[i][pf.getFreePax()[p]][n] += supplementsPerPaxOfferApplicable[i][pf.getFreePax()[p]][n];
						}						
					}
				}
				if (pf.getFreeYouths() != null) {
					for (int currentYouth = 1; currentYouth <= realYouthsInRoom; currentYouth++) {
						for (int p = 0; p < pf.getFreePax().length; p++) {
							if (pf.getFreePax()[p] == currentYouth) {
								for (int n = 0; n < applicable.length; n++) if (applicable[n]) {
									discounts[i][realAdultsInThisRoom + currentYouth][n] += pricePerPax[i][realAdultsInThisRoom + currentYouth][n];
									discounts[i][realAdultsInThisRoom + currentYouth][n] += pricePerBoard[i][realAdultsInThisRoom + currentYouth][n];
									discounts[i][realAdultsInThisRoom + currentYouth][n] += supplementsPerPaxOfferApplicable[i][realAdultsInThisRoom + currentYouth][n];
								}						
							}
						}
					}
				}
				if (pf.getFreeChildren() != null) {
					for (int currentChildren = 1; currentChildren <= realChildrenInThisRoom; currentChildren++) {
						for (int p = 0; p < pf.getFreePax().length; p++) {
							if (pf.getFreePax()[p] == currentChildren) {
								for (int n = 0; n < applicable.length; n++) if (applicable[n]) {
									discounts[i][realAdultsInThisRoom + realYouthsInRoom + currentChildren][n] += pricePerPax[i][realAdultsInThisRoom + realYouthsInRoom + currentChildren][n];
									discounts[i][realAdultsInThisRoom + realYouthsInRoom + currentChildren][n] += pricePerBoard[i][realAdultsInThisRoom + realYouthsInRoom + currentChildren][n];
									discounts[i][realAdultsInThisRoom + realYouthsInRoom + currentChildren][n] += supplementsPerPaxOfferApplicable[i][realAdultsInThisRoom + realYouthsInRoom + currentChildren][n];
								}						
							}
						}
					}
				}
				if (pf.getFreeInfants() != null) {
					for (int currentInfant = 1; currentInfant <= realInfantsInRoom; currentInfant++) {
						for (int p = 0; p < pf.getFreePax().length; p++) {
							if (pf.getFreePax()[p] == currentInfant) {
								for (int n = 0; n < applicable.length; n++) if (applicable[n]) {
									discounts[i][realAdultsInThisRoom + realYouthsInRoom + realChildrenInThisRoom + currentInfant][n] += pricePerPax[i][realAdultsInThisRoom + realYouthsInRoom + realChildrenInThisRoom + currentInfant][n];
									discounts[i][realAdultsInThisRoom + realYouthsInRoom + realChildrenInThisRoom + currentInfant][n] += pricePerBoard[i][realAdultsInThisRoom + realYouthsInRoom + realChildrenInThisRoom + currentInfant][n];
									discounts[i][realAdultsInThisRoom + realYouthsInRoom + realChildrenInThisRoom + currentInfant][n] += supplementsPerPaxOfferApplicable[i][realAdultsInThisRoom + realYouthsInRoom + realChildrenInThisRoom + currentInfant][n];
								}						
							}
						}
					}
				}
			}
			
			for (int i = 0; i < discounts.length; i++) {
				for (int j = 0; j < discounts[i].length; j++) {
					for (int k = 0; k < discounts[i][j].length; k++) {
						discount += discounts[i][j][k];
					}
				}				
			}
			
		} else if (o instanceof RoomPromotionOffer) {
			RoomPromotionOffer rp = (RoomPromotionOffer) o;
			if (roomId.equals(rp.getPromotedRoomCode())) {
				double value0 = getValue(hotel, checkin, checkout, rp.getPromotedRoomCode(), boardId, occupation, false); 
				double value1 = getValue(hotel, checkin, checkout, rp.getPayAsRoomCode(), boardId, occupation, false);
				discount = value1 - value0;
			}
		}

		if (discount != 0) {
			System.out.println("offer " + o.getDescription() + " discounts " + discount);
		}

		return discount;
	}

	/**
	 * check if an offer is applicable
	 * 
	 * @param o the offer
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param occupation an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @return true is we can apply this offer
	 * @throws ParseException
	 */
	private static boolean check(AbstractOffer o, String checkin, String checkout, String roomId, String boardId, int[] occupation) throws ParseException {
		boolean ok = true;
		ok &= o.getId() != null;
		if (ok && o.getMinNightsToApplyThisOffer() > 0) {
			ok &= o.getMinNightsToApplyThisOffer() <= getNumberOfNights(checkin, checkout);
		}
		if (ok && o.getAllowedCheckins() != null) for (DatesRange dr : o.getAllowedCheckins()) {
			if (!(getNight(checkin, dr.getFrom()) <= 0 && getNight(checkin, dr.getTo()) >= 0)) {
				ok = false;
				break;
			}
		}		
		if (ok && o.getAllowedCheckinWeekdays() != null) {
			ok = false;
			int weekDay = new SimpleDateFormat("dd/MM/yyyy").parse(checkin).getDay(); // 0 == sunday
			if (o.getAllowedCheckinWeekdays().isMon() && weekDay == 1
					|| o.getAllowedCheckinWeekdays().isTue() && weekDay == 2
					|| o.getAllowedCheckinWeekdays().isWed() && weekDay == 3
					|| o.getAllowedCheckinWeekdays().isThu() && weekDay == 4
					|| o.getAllowedCheckinWeekdays().isFri() && weekDay == 5
					|| o.getAllowedCheckinWeekdays().isSat() && weekDay == 6
					|| o.getAllowedCheckinWeekdays().isSun() && weekDay == 0
					) ok = true; 
		}
		if (ok && o.getAllowedCheckoutWeekdays() != null) {
			ok = false;
			int weekDay = new SimpleDateFormat("dd/MM/yyyy").parse(checkout).getDay(); // 0 == sunday
			if (o.getAllowedCheckoutWeekdays().isMon() && weekDay == 1
					|| o.getAllowedCheckoutWeekdays().isTue() && weekDay == 2
					|| o.getAllowedCheckoutWeekdays().isWed() && weekDay == 3
					|| o.getAllowedCheckoutWeekdays().isThu() && weekDay == 4
					|| o.getAllowedCheckoutWeekdays().isFri() && weekDay == 5
					|| o.getAllowedCheckoutWeekdays().isSat() && weekDay == 6
					|| o.getAllowedCheckoutWeekdays().isSun() && weekDay == 0
					) ok = true; 
		}
		if (ok && o.getAllowedStays() != null) for (DatesRange dr : o.getAllowedStays()) {
			if (!(getNight(checkout, dr.getFrom()) <= 0 && getNight(checkin, dr.getTo()) >= 0)) {
				ok = false;
				break;
			}
		}
		if (ok && o.getAllowedStayWeekdays() != null) {
			ok = false;
			int weekDayForCheckin = new SimpleDateFormat("dd/MM/yyyy").parse(checkin).getDay(); // 0 == sunday
			for (int i = 0; i < getNumberOfNights(checkin, checkout); i++) {
				int weekDay = (weekDayForCheckin + i) % 7; 
				if (o.getAllowedStayWeekdays().isMon() && weekDay == 1
						|| o.getAllowedStayWeekdays().isTue() && weekDay == 2
						|| o.getAllowedStayWeekdays().isWed() && weekDay == 3
						|| o.getAllowedStayWeekdays().isThu() && weekDay == 4
						|| o.getAllowedStayWeekdays().isFri() && weekDay == 5
						|| o.getAllowedStayWeekdays().isSat() && weekDay == 6
						|| o.getAllowedStayWeekdays().isSun() && weekDay == 0
						) {
					ok = true;
					break;
				}
			}
		}
		if (ok && o.getBoardIds() != null) {
			ok = false;
			for (String b : o.getBoardIds()) if (boardId.equals(b)) {
				ok = true;
				break;
			}
		}
		if (ok && o.getCheckoutTo() != null && !"".equals(o.getCheckoutTo())) {
			ok &= getNumberOfNights(checkout, o.getCheckoutTo()) >= 0;
		}
		if (ok && o.getFormalizationFrom() != null && !"".equals(o.getFormalizationFrom())) {
			ok &= getNumberOfNights(o.getFormalizationFrom(), new SimpleDateFormat("dd/MM/yyyy").format(new Date())) >= 0;
		}
		if (ok && o.getFormalizationTo() != null && !"".equals(o.getFormalizationTo())) {
			ok &= getNumberOfNights(o.getFormalizationTo(), new SimpleDateFormat("dd/MM/yyyy").format(new Date())) <= 0;
		}
		if (ok && o.getMaxNightsToApplyThisOffer() > 0) {
			ok &= getNumberOfNights(checkin, checkout) <= o.getMaxNightsToApplyThisOffer();
//			ok &= o.getFormalizationTo() == null || getNumberOfNights(o.getFormalizationTo(), checkin) <= o.getMaxNightsToApplyThisOffer();
		}
		if (ok && o.getMinNightsToApplyThisOffer() > 0) {
			ok &= getNumberOfNights(checkin, checkout) >= o.getMinNightsToApplyThisOffer();
//			ok &= o.getFormalizationTo() == null || getNumberOfNights(o.getFormalizationTo(), checkin) >= o.getMinNightsToApplyThisOffer();
		}
		if (ok && o.getMinReleaseToApplyThisOffer() > 0) {
			ok &= getNumberOfNights(new SimpleDateFormat("dd/MM/yyyy").format(new Date()), checkin) >= o.getMinReleaseToApplyThisOffer();
		}
		if (ok && o.getRoomIds() != null) {
			ok = false;
			for (String r : o.getRoomIds()) if (roomId.equals(r)) {
				ok = true;
				break;
			}
		}
		if (ok && o.getStockPerRoom() != null) {
			int available = 0;
			for (StockPerRoom s : o.getStockPerRoom()) {
				boolean roomOk = true;
				if (s.getRoomIds() != null && s.getRoomIds().length > 0) {
					roomOk = false;
					for (String r : s.getRoomIds()) {
						if (roomId.equals(r)) {
							roomOk = true;
							break;
						}
					}
				}
				if (roomOk) {
					available += s.getAvailableRooms();
				}					
			}
			int requested = occupation[0];
			ok &= available >= requested;
		}
		
		if (ok && o instanceof StayXPayYOffer) {
			ok &= getNumberOfNights(checkin, checkout) >= ((StayXPayYOffer)o).getStay();
		}
		
		return ok;
	}

	/**
	 * get the value of a room and board combination. It does not check for restrictions.
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId the room id
	 * @param boardId the board id
	 * @param occupation an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @param applyOffers true is offers must be applied. False when used to get the base price before applying offers
	 * @return the value of the room and board combination
	 * @throws ParseException
	 */
	private static double getContractValue(Hotel hotel, Contract c, String checkin, String checkout, String roomId, String boardId, int[] occupation, boolean applyOffers) throws ParseException {

		int numberOfNights = getNumberOfNights(checkin, checkout);

		double value = 0;

		String boardType = "";
		for (ContractedBoardType b : c.getBoardTypes()) if (b.getCode().equals(boardId)) {
			boardType = b.getCavalBoardCategoryCode();
		}

		ContractedRoomType roomType = null;
		if (c.getRoomTypes() != null) for (int i = 0; i < c.getRoomTypes().length; i++) if (c.getRoomTypes()[i].getCode().equals(roomId)) roomType = c.getRoomTypes()[i];

		int maxNumberOfRooms = 0;
		int maxAdultsPerRoom = 0;
		int maxChildrenPerRoom = 0;
		if (maxNumberOfRooms < occupation[0]) maxNumberOfRooms = occupation[0];
		if (maxAdultsPerRoom < occupation[1]) maxAdultsPerRoom = occupation[1];
		if (maxChildrenPerRoom < occupation[2]) maxChildrenPerRoom = occupation[2];

		double[][] pricePerRoom = new double[maxNumberOfRooms][numberOfNights];
		double[][][] pricePerPax = new double[maxNumberOfRooms][numberOfNights][maxAdultsPerRoom + maxChildrenPerRoom];
		double[][][] pricePerBoard = new double[maxNumberOfRooms][numberOfNights][maxAdultsPerRoom + maxChildrenPerRoom];
		double[][][] discountsPerPax = new double[maxNumberOfRooms][numberOfNights][maxAdultsPerRoom + maxChildrenPerRoom];

		if (roomType != null) {
			int numberOfRooms = occupation[0];
			int adultsPerRoom = occupation[1];
			int childrenPerRoom = occupation[2];

			int[][] childrenAges = new int[numberOfRooms][childrenPerRoom];

			if (occupation.length > 3) {
				int[] passedChildrenAges = Arrays.copyOfRange(occupation, 3, occupation.length);
				Arrays.sort(passedChildrenAges);
				int pos = 0;			
				for (int j = childrenPerRoom - 1; j >= 0; j--) for (int i = 0; i < numberOfRooms; i++)childrenAges[i][j] = (passedChildrenAges.length > pos)?passedChildrenAges[pos]:11;
			} else {
				for (int j = childrenPerRoom - 1; j >= 0; j--) for (int i = 0; i < numberOfRooms; i++)childrenAges[i][j] = 11;
			}

			for (Rate r : c.getRate()) {
				boolean[] applicable = new boolean[numberOfNights];
				boolean applicableForAnyDate = false; 
				for (DatesRange dr : r.getDatesRanges()) {
					Date dCheckIn = new SimpleDateFormat("dd/MM/yyyy").parse(checkin);
					Date dfrom = new SimpleDateFormat("dd/MM/yyy").parse(dr.getFrom());
					Date dto = new SimpleDateFormat("dd/MM/yyy").parse(dr.getTo());					 
					if ( (dCheckIn.after(dfrom) && dCheckIn.before(dto)) || dCheckIn.equals(dfrom) || dCheckIn.equals(dto))	for (int i = getNight(checkin, dr.getFrom()); i <= getNight(checkin, dr.getTo()); i++) if (i >= 0 && i < applicable.length) {
						applicable[i] = true;
						applicableForAnyDate = true;
					}
				}

				if (applicableForAnyDate) for (RatePerRoom rpr : r.getRatesPerRoom()) if (roomId.equals(rpr.getRoomId())) {
					if (rpr.getRatesPerBoard() != null) for (RatePerRoomAndBoard rpb : rpr.getRatesPerBoard()) if (boardId.equals(rpb.getBoardCode())) {
						double basePrice[] = getBasePrice(rpr, rpb);							
						for (int i = 0; i < numberOfNights; i++) if (applicable[i]) {

							for (int room = 0; room < numberOfRooms; room++) {

								if (roomType.isPricePerRoom()) pricePerRoom[room][i] = basePrice[0];
								else for (int j = 0; j < adultsPerRoom + childrenPerRoom; j++) pricePerPax[room][i][j] = basePrice[0];

								for (int j = 0; j < adultsPerRoom + childrenPerRoom; j++) pricePerBoard[room][i][j] = basePrice[1];

								if (rpb.getPaxSupplementsAndDiscounts() != null) for (PaxPrice pp : rpb.getPaxSupplementsAndDiscounts()) {
									if ((pp.getPax() == 1 && 1 == pricePerPax[room][i].length) || (pp.getPax() > 1 && pp.getPax() <= pricePerPax[room][i].length)) {								
									double base = pp.isOnBasePrice()?pricePerPax[room][i][pp.getPax() - 1]:0;
									base += pp.isOnBoardPrice()?pricePerBoard[room][i][pp.getPax() - 1]:0;
									discountsPerPax[room][i][pp.getPax() - 1] = applySupplement(base, pp.getValue()) - base;
									}
								}


								int posYouth = 0;
								int posChild = 0;
								int posinfant = 0;
								int posPax = adultsPerRoom + childrenPerRoom - 1;
								for (int j = 0; j < childrenAges[room].length; j++) if (posPax >= roomType.getMinAdultsForChildDiscount()) {
									if (c.getInfantMaxAge() != null && childrenAges[room][j] <= c.getInfantMaxAge()) {
										if (c.getInfantsAlwaysFree()) {
											double base = pricePerPax[room][i][posPax];
											base += pricePerBoard[room][i][posPax];
											discountsPerPax[room][i][posPax] = applySupplement(base, "-100%") - base;
										} else if (rpb.getInfantSupplementsAndDiscounts() != null) {
											for (int k = 0; k < rpb.getInfantSupplementsAndDiscounts().length; k++) if (posinfant == rpb.getInfantSupplementsAndDiscounts()[k].getPax() - 1) {
												double base = (rpb.getInfantSupplementsAndDiscounts()[k].isOnBasePrice())?pricePerPax[room][i][posPax]:0;
												base += rpb.getInfantSupplementsAndDiscounts()[k].isOnBoardPrice()?pricePerBoard[room][i][posPax]:0;
												discountsPerPax[room][i][posPax] = applySupplement(base, rpb.getInfantSupplementsAndDiscounts()[k].getValue()) - base;
											}
										}
										posinfant++;
									} else if (c.getChildMaxAge() != null && childrenAges[room][j] <= c.getChildMaxAge()) {
										if (rpb.getChildSupplementsAndDiscounts() != null) {
											for (int k = 0; k < rpb.getChildSupplementsAndDiscounts().length; k++) if (posChild == rpb.getChildSupplementsAndDiscounts()[k].getPax() - 1) {
												double base = (rpb.getChildSupplementsAndDiscounts()[k].isOnBasePrice())?pricePerPax[room][i][posPax]:0;
												base += rpb.getChildSupplementsAndDiscounts()[k].isOnBoardPrice()?pricePerBoard[room][i][posPax]:0;
												discountsPerPax[room][i][posPax] = applySupplement(base, rpb.getChildSupplementsAndDiscounts()[k].getValue()) - base;
											}
										}
										posChild++;
									} else if (c.getYouthMaxAge() != null && childrenAges[room][j] <= c.getYouthMaxAge()) {
										if (rpb.getYouthSupplementsAndDiscounts() != null) {
											for (int k = 0; k < rpb.getYouthSupplementsAndDiscounts().length; k++) if (posYouth == rpb.getYouthSupplementsAndDiscounts()[k].getPax() - 1) {
												double base = (rpb.getYouthSupplementsAndDiscounts()[k].isOnBasePrice())?pricePerPax[room][i][posPax]:0;
												base += rpb.getYouthSupplementsAndDiscounts()[k].isOnBoardPrice()?pricePerBoard[room][i][posPax]:0;
												discountsPerPax[room][i][posPax] = applySupplement(base, rpb.getYouthSupplementsAndDiscounts()[k].getValue()) - base;
											}
										}
										posYouth++;
									}
									posPax--;
								}

							}
						}
					}
				}

			}

		}
		
		double total = getBookingBasePrice(pricePerRoom, pricePerPax, pricePerBoard);

		double[] supplementsPerBookingNoOfferApplicable = new double[numberOfNights];
		double[][] supplementsPerRoomNoOfferApplicable = new double[maxNumberOfRooms][numberOfNights];
		double[][][] supplementsPerPaxNoOfferApplicable = new double[maxNumberOfRooms][numberOfNights][maxAdultsPerRoom + maxChildrenPerRoom];

		double[] supplementsPerBookingOfferApplicable = new double[numberOfNights];
		double[][] supplementsPerRoomOfferApplicable = new double[maxNumberOfRooms][numberOfNights];
		double[][][] supplementsPerPaxOfferApplicable = new double[maxNumberOfRooms][numberOfNights][maxAdultsPerRoom + maxChildrenPerRoom];

		applySupplements(hotel, c, checkin, checkout, roomId, boardId, occupation, pricePerRoom, pricePerPax, pricePerBoard, discountsPerPax, supplementsPerBookingNoOfferApplicable, supplementsPerRoomNoOfferApplicable, supplementsPerPaxNoOfferApplicable, supplementsPerBookingOfferApplicable, supplementsPerRoomOfferApplicable, supplementsPerPaxOfferApplicable);

		applyMinimumStaySupplements(hotel, c, checkin, checkout, roomId, boardId, getBookingExplodedPriceForOffers(pricePerRoom, pricePerPax, pricePerBoard, discountsPerPax, supplementsPerBookingOfferApplicable, supplementsPerRoomOfferApplicable, supplementsPerPaxOfferApplicable), supplementsPerBookingNoOfferApplicable, supplementsPerPaxNoOfferApplicable);
		
		List<Double> lSpecialDinner = applySpecialDinnerPerDate(hotel, c, checkin, checkout, occupation, boardId);
		
		value = getBookingTotal(pricePerRoom, pricePerPax, pricePerBoard, discountsPerPax, supplementsPerBookingNoOfferApplicable, supplementsPerRoomNoOfferApplicable, supplementsPerPaxNoOfferApplicable, supplementsPerBookingOfferApplicable, supplementsPerRoomOfferApplicable, supplementsPerPaxOfferApplicable, lSpecialDinner);

		System.out.println("Booking value before offers..." + value);
		
		if (applyOffers) {
			double valueWithOffers = applyOffers(hotel, c, checkin, checkout, roomId, boardId, boardType, occupation, value, pricePerRoom, pricePerPax, pricePerBoard, discountsPerPax, supplementsPerBookingNoOfferApplicable, supplementsPerRoomNoOfferApplicable, supplementsPerPaxNoOfferApplicable, supplementsPerBookingOfferApplicable, supplementsPerRoomOfferApplicable, supplementsPerPaxOfferApplicable);			
			if (valueWithOffers > 0 && value > valueWithOffers) value = valueWithOffers;
		}

		// CHECK PRICE FOR EVERY DAY
		
		boolean priceForEveryDay = true;
		for (int room = 0; room < pricePerRoom.length; room++) {
			for (int night = 0; night < pricePerRoom[room].length; night++) {
				priceForEveryDay &= pricePerRoom[room][night] > 0 || (pricePerPax[room][night].length > 0 && pricePerPax[room][night][0] > 0); 
			}
		}
		
		return (priceForEveryDay)?value:0;
	}


	/**
	 * totalizes the booking price 
	 * 
	 * @param pricePerRoom price for each room before applying offers
	 * @param pricePerPax price for each pax before applying offers
	 * @param pricePerBoard price for each pax due to board
	 * @param discountsPerPax third pax and child discounts (and single use supplement) per pax
	 * @param supplementsPerBookingNoOfferApplicable supplements per booking that must be left out of the offers
	 * @param supplementsPerRoomNoOfferApplicable supplements per room that must be left out of the offers
	 * @param supplementsPerPaxNoOfferApplicable supplements per pax that must be left out of the offers
	 * @param supplementsPerBookingOfferApplicable supplements per booking that can be taken in account by the offers
	 * @param supplementsPerRoomOfferApplicable supplements per room that can be taken in account by the offers
	 * @param supplementsPerPaxOfferApplicable supplements per pax that can be taken in account by the offers
	 * @param lSpecialDinner supplements for Christmas, new year's eve
	 * @return the booking total price
	 */
	private static double getBookingTotal(double[][] pricePerRoom,
			double[][][] pricePerPax, double[][][] pricePerBoard,
			double[][][] discountsPerPax,
			double[] supplementsPerBookingNoOfferApplicable,
			double[][] supplementsPerRoomNoOfferApplicable,
			double[][][] supplementsPerPaxNoOfferApplicable,
			double[] supplementsPerBookingOfferApplicable,
			double[][] supplementsPerRoomOfferApplicable,
			double[][][] supplementsPerPaxOfferApplicable, List<Double> lSpecialDinner) {

		boolean[] nights = new boolean[pricePerRoom[0].length];
		Arrays.fill(nights, true);
		double total = getBookingBasePrice(nights, pricePerRoom, pricePerPax, pricePerBoard);

		for (int i = 0; i < supplementsPerBookingNoOfferApplicable.length; i++) {
			total += supplementsPerBookingNoOfferApplicable[i];
			total += supplementsPerBookingOfferApplicable[i];
		}
		
		for (int i = 0; i < supplementsPerRoomNoOfferApplicable.length; i++) {
			for (int room = 0; room < supplementsPerRoomNoOfferApplicable[i].length; room++) {
				total += supplementsPerRoomNoOfferApplicable[i][room];
				total += supplementsPerRoomOfferApplicable[i][room];
				for (int j = 0; j < discountsPerPax[i][room].length; j++) total += discountsPerPax[i][room][j]; 
				for (int j = 0; j < supplementsPerPaxNoOfferApplicable[i][room].length; j++) total += supplementsPerPaxNoOfferApplicable[i][room][j]; 
				for (int j = 0; j < supplementsPerPaxOfferApplicable[i][room].length; j++) total += supplementsPerPaxOfferApplicable[i][room][j]; 
			}
		}
		
		for (Double specialDinner : lSpecialDinner) {
			total += specialDinner.doubleValue();			
		}
		
		return total;
	}

	/**
	 * totalizes the booking price but splitting the result by night. Used when applying offers
	 * 
	 * @param pricePerRoom price for each room before applying offers
	 * @param pricePerPax price for each pax before applying offers
	 * @param pricePerBoard price for each pax due to board
	 * @param discountsPerPax third pax and child discounts (and single use supplement) per pax
	 * @param supplementsPerBookingOfferApplicable supplements per booking that can be taken in account by the offers
	 * @param supplementsPerRoomOfferApplicable supplements per room that can be taken in account by the offers
	 * @param supplementsPerPaxOfferApplicable supplements per pax that can be taken in account by the offers
	 * @return the booking totals per night
	 */
	private static double[] getBookingExplodedPriceForOffers(double[][] pricePerRoom,
			double[][][] pricePerPax, double[][][] pricePerBoard,
			double[][][] discountsPerPax,
			double[] supplementsPerBookingOfferApplicable,
			double[][] supplementsPerRoomOfferApplicable,
			double[][][] supplementsPerPaxOfferApplicable) {

		double[] total = getBookingBasePriceForOffers(pricePerRoom, pricePerPax, pricePerBoard);

		for (int i = 0; i < supplementsPerBookingOfferApplicable.length; i++) {
			total[i] += supplementsPerBookingOfferApplicable[i];
		}

		for (int i = 0; i < supplementsPerRoomOfferApplicable.length; i++) {
			for (int night = 0; night < supplementsPerRoomOfferApplicable[i].length; night++) {
				total[night] += supplementsPerRoomOfferApplicable[i][night];
				for (int j = 0; j < discountsPerPax[i][night].length; j++) total[night] += discountsPerPax[i][night][j]; 
				for (int j = 0; j < supplementsPerPaxOfferApplicable[i][night].length; j++) total[night] += supplementsPerPaxOfferApplicable[i][night][j]; 
			}
		}

		return total;
	}

	/**
	 * apply mandatory supplements (not third pax or child discounts)
	 * 
	 * @param hotel the hotel
	 * @param c the contract
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId room id
	 * @param boardId board id
	 * @param occupation an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @param pricePerRoom price for each room before applying offers
	 * @param pricePerPax price for each pax before applying offers
	 * @param pricePerBoard price for each pax due to board
	 * @param discountsPerPax third pax and child discounts (and single use supplement) per pax
	 * @param supplementsPerBookingNoOfferApplicable supplements per booking that must be left out of the offers
	 * @param supplementsPerRoomNoOfferApplicable supplements per room that must be left out of the offers
	 * @param supplementsPerPaxNoOfferApplicable supplements per pax that must be left out of the offers
	 * @param supplementsPerBookingOfferApplicable supplements per booking that can be taken in account by the offers
	 * @param supplementsPerRoomOfferApplicable supplements per room that can be taken in account by the offers
	 * @param supplementsPerPaxOfferApplicable supplements per pax that can be taken in account by the offers
	 * @throws ParseException
	 */
	private static void applySupplements(Hotel hotel, Contract c, String checkin,
			String checkout, String roomId, String boardId,
			int[] occupation, double[][] pricePerRoom,
			double[][][] pricePerPax, double[][][] pricePerBoard,
			double[][][] discountsPerPax,
			double[] supplementsPerBookingNoOfferApplicable,
			double[][] supplementsPerRoomNoOfferApplicable,
			double[][][] supplementsPerPaxNoOfferApplicable,
			double[] supplementsPerBookingOfferApplicable,
			double[][] supplementsPerRoomOfferApplicable,
			double[][][] supplementsPerPaxOfferApplicable) throws ParseException {

		if (c.getSupplementsOrDiscounts() != null) for (SupplementOrDiscount s : c.getSupplementsOrDiscounts()) {
			if (check(s, checkin, checkout, roomId, boardId, occupation)) {
				if (s.isMandatory() && !s.isOnRequest() && !s.isPayAtHotel()) {
					double[] supplementsPerBooking = supplementsPerBookingNoOfferApplicable;
					double[][] supplementsPerRoom = supplementsPerRoomNoOfferApplicable;
					double[][][] supplementsPerPax = supplementsPerPaxNoOfferApplicable;

					if (s.isSubjectToOffers()) {
						supplementsPerBooking = supplementsPerBookingOfferApplicable;
						supplementsPerRoom = supplementsPerRoomOfferApplicable;
						supplementsPerPax = supplementsPerPaxOfferApplicable;
					}

					int from = 0;
					int to = supplementsPerBooking.length;
					if ("BOOKING".equals(s.getApplyTo())) {
						to = 1;
					} else { // NIGHT
						if (s.getFromStayDate() != null && !"".equals(s.getFromStayDate())) from = getNight(checkin, s.getFromStayDate()); 						
						if (s.getToStayDate() != null && !"".equals(s.getToStayDate())) to = getNight(checkin, s.getToStayDate()) + 1; 						
					}
					if (from < 0) from = 0;
					if (to >= supplementsPerBooking.length) to = supplementsPerBooking.length;


					for (int i = from; i < to; i++) {
						if ("PAX".equals(s.getApplyPer())) {
							for (int j = 0; j < supplementsPerPax.length; j++) {
								for (int k = 0; k < supplementsPerPax[j][i].length; k++) {
									supplementsPerPax[j][i][k] += applySupplement(pricePerPax[j][i][k], s.getAmount()) - pricePerPax[j][i][k];
								}
							}
						} else if ("SERVICE".equals(s.getApplyPer())) {
							String boardCategory = getBoardcategory(c, boardId);
							int services = 0;
							if ("BB".equals(boardCategory)) services = 1;
							else if ("HB".equals(boardCategory)) services = 2;
							else if ("FB".equals(boardCategory)) services = 3;
							else if ("AI".equals(boardCategory)) services = 4;

							for (int j = 0; j < supplementsPerPax.length; j++) {
								for (int k = 0; k < supplementsPerPax[j][i].length; k++) {
									supplementsPerPax[j][i][k] += services * (applySupplement(pricePerBoard[j][i][k], s.getAmount()) - pricePerBoard[j][i][k]);
								}
							}
						} else if ("ROOM".equals(s.getApplyPer())) {
							for (int j = 0; j < supplementsPerRoom.length; j++) {
								supplementsPerRoom[j][i] += applySupplement(pricePerRoom[j][i], s.getAmount()) - pricePerRoom[j][i];
							}
						} else if ("BOOKING".equals(s.getApplyPer())) {
							supplementsPerBooking[i] += applySupplement(getBookingBasePrice(pricePerRoom, pricePerPax, pricePerBoard), s.getAmount());
						}
					}

				}
			}
		}
		
	}

	/**
	 * look for caval's board category group for a board in a contract 
	 * 
	 * @param c the contract
	 * @param boardId the board id
	 * @return caval's board category group code
	 */
	private static String getBoardcategory(Contract c, String boardId) {
		String cat = "";
		for (ContractedBoardType b : c.getBoardTypes()) {
			if (boardId.equals(b.getCode())) cat = b.getCavalBoardCategoryCode();
		}
		return cat;
	}

	/**
	 * totalizes booking base price
	 * 
	 * @param pricePerRoom prices per room and night
	 * @param pricePerPax prices per pax and night
	 * @param pricePerBoard price per board and night
	 * @return the total
	 */
	private static double getBookingBasePrice(double[][] pricePerRoom,
			double[][][] pricePerPax, double[][][] pricePerBoard) {
		boolean[] nights = new boolean[pricePerRoom[0].length];
		Arrays.fill(nights, true);		
		return getBookingBasePrice(nights, pricePerRoom, pricePerPax, pricePerBoard);
	}

	/**
	 * totalizes booking base price for selected nights
	 * 
	 * @param nights nights we are interested in
	 * @param pricePerRoom prices per room and night
	 * @param pricePerPax prices per pax and night
	 * @param pricePerBoard price per board and night
	 * @return the total for selected nights
	 */
	private static double getBookingBasePrice(boolean[] nights, double[][] pricePerRoom,
			double[][][] pricePerPax, double[][][] pricePerBoard) {
		double total = 0;
		for (int room = 0; room < pricePerRoom.length; room++) {
			for (int night = 0; night < pricePerRoom[room].length; night++) if (nights[night]) {
				total += pricePerRoom[room][night];
				for (int j = 0; j < pricePerPax[room][night].length; j++) total += pricePerPax[room][night][j]; 
				for (int j = 0; j < pricePerPax[room][night].length; j++) total += pricePerBoard[room][night][j]; 
			}
		}
		return total;
	}

	/**
	 *  totalizes the booking base price. Used when applying offers
	 *  
	 * @param pricePerRoom price per room and night
	 * @param pricePerPax perice per pax and night
	 * @param pricePerBoard price per board and night
	 * @return total per night
	 */
	private static double[] getBookingBasePriceForOffers(double[][] pricePerRoom,
			double[][][] pricePerPax, double[][][] pricePerBoard) {
		double[] total = new double[pricePerRoom[0].length];
		for (int room = 0; room < pricePerRoom.length; room++) {
			for (int night = 0; night < pricePerRoom[room].length; night++) {
				total[night] += pricePerRoom[room][night];
				for (int j = 0; j < pricePerPax[room][night].length; j++) total[night] += pricePerPax[room][night][j]; 
				for (int j = 0; j < pricePerPax[room][night].length; j++) total[night] += pricePerBoard[room][night][j]; 
			}
		}
		return total;
	}

	/**
	 * checks if a mandatory supplement must be applied
	 * 
	 * @param s the supplement or discount
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @param roomId room id
	 * @param boardId board id
	 * @param occupations an array like [number of rooms, adults per room, children per room, age first child, age second child, ...]
	 * @return true if supplement must be applied
	 * @throws ParseException
	 */
	private static boolean check(SupplementOrDiscount s, String checkin, String checkout, String roomId, String boardId, int[] occupations) throws ParseException {
		
		boolean ok = true;
		
		if (ok && s.getCheckinWeekDays() != null) {
			ok = false;
			int weekDay = new SimpleDateFormat("dd/MM/yyyy").parse(checkin).getDay(); // 0 == sunday
			if (s.getCheckinWeekDays().isMon() && weekDay == 1
					|| s.getCheckinWeekDays().isTue() && weekDay == 2
					|| s.getCheckinWeekDays().isWed() && weekDay == 3
					|| s.getCheckinWeekDays().isThu() && weekDay == 4
					|| s.getCheckinWeekDays().isFri() && weekDay == 5
					|| s.getCheckinWeekDays().isSat() && weekDay == 6
					|| s.getCheckinWeekDays().isSun() && weekDay == 0
					) ok = true; 
		}
		if (ok && s.getCheckoutWeekDays() != null) {
			ok = false;
			int weekDay = new SimpleDateFormat("dd/MM/yyyy").parse(checkout).getDay(); // 0 == sunday
			if (s.getCheckoutWeekDays().isMon() && weekDay == 1
					|| s.getCheckoutWeekDays().isTue() && weekDay == 2
					|| s.getCheckoutWeekDays().isWed() && weekDay == 3
					|| s.getCheckoutWeekDays().isThu() && weekDay == 4
					|| s.getCheckoutWeekDays().isFri() && weekDay == 5
					|| s.getCheckoutWeekDays().isSat() && weekDay == 6
					|| s.getCheckoutWeekDays().isSun() && weekDay == 0
					) ok = true; 
		}
		
		if (ok && s.getFromCheckinDate() != null && !"".equals(s.getFromCheckinDate())) {
			ok &= getNumberOfNights(checkin, s.getFromCheckinDate()) >= 0;
		}
		if (ok && s.getToCheckinDate() != null && !"".equals(s.getToCheckinDate())) {
			ok &= getNumberOfNights(checkin, s.getToCheckinDate()) <= 0;
		}
		if (ok && s.getFromStayDate() != null && !"".equals(s.getFromStayDate())) {
			ok &= getNumberOfNights(checkout, s.getFromStayDate()) <= 0;
		}
		if (ok && s.getToStayDate() != null && !"".equals(s.getToStayDate())) {
			ok &= getNumberOfNights(checkin, s.getToStayDate()) >= 0;
		}
		if (ok && s.getBoardCodes() != null) {
			ok = false;
			for (String b : s.getBoardCodes()) if (boardId.equals(b)) {
				ok = true;
				break;
			}
		}
		if (ok && s.getRoomCodes() != null) {
			ok = false;
			for (String r : s.getRoomCodes()) if (roomId.equals(r)) {
				ok = true;
				break;
			}
		}
		
		return ok;
	}

	/**
	 * get the base price for a room and board combination
	 * 
	 * @param rpr room data in contract 
	 * @param rpb board data in contract
	 * @return the base price
	 */
	private static double[] getBasePrice(RatePerRoom rpr, RatePerRoomAndBoard rpb) {
		double[] basePrice = {0,0};
		if (rpb.getReferenceBoardType() != null && !"".equals(rpb.getReferenceBoardType()) && !rpb.getBoardCode().equals(rpb.getReferenceBoardType())) {
			for (RatePerRoomAndBoard rpbx : rpr.getRatesPerBoard()) if (rpb.getReferenceBoardType().equals(rpbx.getBoardCode())) {
				basePrice = getBasePrice(rpr, rpbx);
			}							
			basePrice[1] = applySupplement(basePrice[0], rpb.getBasePrice()) - basePrice[0];
		} else {
			basePrice[0] = Double.parseDouble(rpb.getBasePrice());
		}
		return basePrice;
	}

	/**
	 * applies supplement, discount or final price
	 * 
	 * @param basePrice the base price
	 * @param supplement the supplement or dicount (+10%, -15, 34.77)
	 * @return the price after applying the supplement or dicount
	 */
	private static double applySupplement(double basePrice, String supplement) {
		double price = basePrice;		
		if (supplement != null) {
			supplement = supplement.trim();
			if (!"".equals(supplement)) {
				if (supplement.startsWith("+") || (!supplement.startsWith("-") && supplement.endsWith("%")) ) {
					if (supplement.endsWith("%")) {
						price = price * (100d + Double.parseDouble(supplement.substring(1, supplement.length() - 1))) / 100d;
					} else {
						price = price + Double.parseDouble(supplement.substring(1));
					}
				} else if (supplement.startsWith("-")) {
					if (supplement.endsWith("%")) {
						price = price * (100d - Double.parseDouble(supplement.substring(1, supplement.length() - 1))) / 100d;
					} else {
						price = price - Double.parseDouble(supplement.substring(1));
					}
				} else {
					price = Double.parseDouble(supplement);
				}
			}
		}
		return price;
	}

	/**
	 * calculate night number
	 * 
	 * @param checkin the check in date in dd/MM/yyyy format
	 * @param date date in dd/MM/yyyy format
	 * @return night number from check in date
	 * @throws ParseException
	 */
	private static int getNight(String checkin, String date) throws ParseException {
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");		
		return new Double((sdf.parse(date).getTime() - sdf.parse(checkin).getTime()) / (24 * 60 * 60 * 1000)).intValue();
	}

	/**
	 * calculate number of nights
	 * 
	 * @param checkin check in date in dd/MM/yyyy format
	 * @param checkout check out date in dd/MM/yyyy format
	 * @return number of nights
	 * @throws ParseException
	 */
	private static int getNumberOfNights(String checkin, String checkout) throws ParseException {
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
		return new Double((sdf.parse(checkout).getTime() - sdf.parse(checkin).getTime()) / (24 * 60 * 60 * 1000)).intValue();
	}
	
	private static List<Double> applySpecialDinnerPerDate(Hotel hotel, Contract c, String checkin, String checkout, int[] occupation, String boardId) throws ParseException{
		List<Double> lSpecialsDinners = new ArrayList<Double>();		
		if (c.getSpecialDinnerPerDate() != null) for (Dinner dinner : c.getSpecialDinnerPerDate()) {
			
			//Comprobamos que la pensión está contenida
			boolean boardFound = false;
			if (dinner.getAppliableForBoards() != null) for (String current : dinner.getAppliableForBoards()) {
				boardFound |= current.equalsIgnoreCase(boardId);
			}
			
			if (boardFound) {
				Date dinnerDate = new SimpleDateFormat("dd/MM/yyyy").parse(dinner.getDate() + "/" + (new SimpleDateFormat("yyyy").format(new Date())));
				Date checkIn = new SimpleDateFormat("dd/MM/yyyy").parse(checkin);
				Date checkOut = new SimpleDateFormat("dd/MM/yyyy").parse(checkout);
				
				if ( ( dinnerDate.after(checkIn) && dinnerDate.before(checkOut))  || dinnerDate.equals(checkIn) || dinnerDate.equals(checkOut) ){
					
					double total = 0d;
					double totalPax = 0d;
					if ( dinner.getAmountPerPax() > 0 && occupation[1] > 0) {
						 totalPax = dinner.getAmountPerPax();
						 total += occupation[1] * totalPax;
					}
					
					//Cálculo para el primer niño
					if ( occupation[2] > 0 && dinner.getAmountOrDiscountPerChild() != null){					
						total += applySupplement(totalPax, dinner.getAmountOrDiscountPerChild(0).getValue()); 
					}
					
					//Cálculo para el segundo niño y siguientes
					if ( occupation[2] > 1 && dinner.getAmountOrDiscountPerChild() != null){
						String val = dinner.getAmountOrDiscountPerChild(0).getValue();
						if (dinner.getAmountOrDiscountPerChild().length > 1 ){
							val = dinner.getAmountOrDiscountPerChild(1).getValue();
						}
						total += applySupplement( (occupation[2] - 1) * totalPax, val); 
					}
					
					lSpecialsDinners.add(total);
				}
			}
		}
		
		return lSpecialsDinners;
	}


}
